/**
 * USE: getA()
 * 		if positive infinite, getC()
 * 		else getB(A)
 * 
 * @author Phil
 *
 */
public class Segment {

	private Point p1;
	private Point p2;
	
	public Segment(Point p1, Point p2){
		this.p1 = p1;
		this.p2 = p2;
	}

	/**
	 * Returns the Point that is the intersection of this Segment and the one provided
	 * If the method detects the segments are aligned and intersect, it throws an exception
	 * If no intersection at all is found, returns null
	 * 
	 * @param s
	 * @return
	 * @throws IntersectsAsSegmentException
	 */
	public Point pointIntersection(Segment s) throws IntersectsAsSegmentException{
		Point result = null;
		double a1 = this.getA();
		double a2 = s.getA();
		double b1;
		double b2;
		double c1;
		double c2;
		
		
		/***********************************************************************************/
		//if THIS is a point-like segment
		if (a1 == Double.NEGATIVE_INFINITY){
			//if s is also a point-like segment
			if (a2 == Double.NEGATIVE_INFINITY){
				double x = this.getP1().getX();
				double y = this.getP1().getY();
				if(x == s.getP1().getX() && y == s.getP1().getY()){
					result = new Point(x,y);
				}
			}
			//if s is vertical
			else if (a2 == Double.POSITIVE_INFINITY){
				try{
					c2 = s.getC();
					double x = this.getP1().getX();
					double y = this.getP1().getY();
					double sMinY = Math.min(s.getP1().getY(),s.getP2().getY());
					double sMaxY = Math.max(s.getP1().getY(),s.getP2().getY());
					if(x==c2 && y>=sMinY && y<=sMaxY){
						result = new Point(x,y);
					}
				} catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
			}
			//if s is not vertical, not point-like
			else{
				try{
					b2 = s.getB();
					double x = this.getP1().getX();
					double y = this.getP1().getY();
					double sMinX = Math.min(s.getP1().getX(),s.getP2().getX());
					double sMaxX = Math.max(s.getP1().getX(),s.getP2().getX());
					double sMinY = Math.min(s.getP1().getY(),s.getP2().getY());
					double sMaxY = Math.max(s.getP1().getY(),s.getP2().getY());
					if(a2*x+b2==y &&
							x>=sMinX && x<=sMaxX &&
							y>=sMinY && y<=sMaxY){
							result = new Point(x,y);
					}
				}catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
			}
		}
		
		/***********************************************************************************/
		//if a2 is a point-like segment
		else if(a2 == Double.NEGATIVE_INFINITY){
			//if THIS is also a point-like segment
			if (a1 == Double.NEGATIVE_INFINITY){
				double x = s.getP1().getX();
				double y = s.getP1().getY();
				if(x == this.getP1().getX() && y == this.getP1().getY()){
					result = new Point(x,y);
				}
			}
			//if THIS is vertical
			else if (a1 == Double.POSITIVE_INFINITY){
				try{
					c1 = this.getC();
					double x = s.getP1().getX();
					double y = s.getP1().getY();
					double thisMinY = Math.min(this.getP1().getY(),this.getP2().getY());
					double thisMaxY = Math.max(this.getP1().getY(),this.getP2().getY());
					if(x==c1 && y>=thisMinY && y<=thisMaxY){
						result = new Point(x,y);
					}
				} catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
			}
			//if THIS is not vertical, not point-like
			else{
				try{
					b1 = this.getB();
					double x = s.getP1().getX();
					double y = s.getP1().getY();
					double thisMinX = Math.min(this.getP1().getX(),this.getP2().getX());
					double thisMaxX = Math.max(this.getP1().getX(),this.getP2().getX());
					double thisMinY = Math.min(this.getP1().getY(),this.getP2().getY());
					double thisMaxY = Math.max(this.getP1().getY(),this.getP2().getY());
					if(a1*x+b1==y &&
							x>=thisMinX && x<=thisMaxX &&
							y>=thisMinY && y<=thisMaxY){
							result = new Point(x,y);
					}
				}catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
			}
		}
		
		/***********************************************************************************/
		//From here on we assume no point-like segments are involved.
		//if s and THIS are vertical
		else if (a2==Double.POSITIVE_INFINITY && a1==Float.POSITIVE_INFINITY){
			//if they align, exception
			try {
				if(s.getC()==this.getC()){
					throw(new IntersectsAsSegmentException("Both segments are on the same line, call segmentIntersection instead"));
				}
			} catch (InvalidSlopeException e) {
				e.printStackTrace();
			}
			//else, we return null
		}
		
		/***********************************************************************************/
		//if only s is vertical
		else if (a2==Double.POSITIVE_INFINITY){
			try {
				b1 = this.getB();
				double thisMinX = Math.min(this.getP1().getX(),this.getP2().getX());
				double thisMaxX = Math.max(this.getP1().getX(),this.getP2().getX());
				double thisMinY = Math.min(this.getP1().getY(),this.getP2().getY());
				double thisMaxY = Math.max(this.getP1().getY(),this.getP2().getY());
				double sMinX = Math.min(s.getP1().getX(),s.getP2().getX());
				double sMaxX = Math.max(s.getP1().getX(),s.getP2().getX());
				double sMinY = Math.min(s.getP1().getY(),s.getP2().getY());
				double sMaxY = Math.max(s.getP1().getY(),s.getP2().getY());
				//if the intersection found is within the bounds of the segments
				double x = s.getC();
				double y = a1*s.getC()+b1;
				if (x>=thisMinX && x<=thisMaxX &&
						x>=sMinX && x<=sMaxX &&
						y>=thisMinY && y<=thisMaxY &&
						y>=sMinY && y<=sMaxY){
						result = new Point(x,y);
				}
			} catch (InvalidSlopeException e) {
				e.printStackTrace();
			}	
		}
		
		/***********************************************************************************/
		//if only THIS is vertical
		else if (a1==Double.POSITIVE_INFINITY){
			try {
				b2 = s.getB();
				double thisMinX = Math.min(this.getP1().getX(),this.getP2().getX());
				double thisMaxX = Math.max(this.getP1().getX(),this.getP2().getX());
				double thisMinY = Math.min(this.getP1().getY(),this.getP2().getY());
				double thisMaxY = Math.max(this.getP1().getY(),this.getP2().getY());
				double sMinX = Math.min(s.getP1().getX(),s.getP2().getX());
				double sMaxX = Math.max(s.getP1().getX(),s.getP2().getX());
				double sMinY = Math.min(s.getP1().getY(),s.getP2().getY());
				double sMaxY = Math.max(s.getP1().getY(),s.getP2().getY());
				//if the intersection found is within the bounds of the segments
				double x = this.getC();
				double y = a2*this.getC()+b2;
				if (x>=thisMinX && x<=thisMaxX &&
						x>=sMinX && x<=sMaxX &&
						y>=thisMinY && y<=thisMaxY &&
						y>=sMinY && y<=sMaxY){
						result = new Point(x,y);
				}
			} catch (InvalidSlopeException e) {
				e.printStackTrace();
			}
		}
		
		/***********************************************************************************/
		//if neither is vertical
		else {	
			//if both segments are parallel
			if (a1==a2){
				//if they intersect at all, exception
				try {
					b1 = this.getB();
					b2 = s.getB();
					if(b1==b2){
						throw(new IntersectsAsSegmentException("Both segments are on the same line, call segmentIntersection instead"));
					}
				} catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
				//else, we return null
			}
			//if they're not parallel
			else {
				//System.out.println("Calculating point intersection between two nonvertical non parallel segments");
				try {
					b1 = this.getB();
					b2 = s.getB();
					double thisMinX = Math.min(this.getP1().getX(),this.getP2().getX());
					double thisMaxX = Math.max(this.getP1().getX(),this.getP2().getX());
					double thisMinY = Math.min(this.getP1().getY(),this.getP2().getY());
					double thisMaxY = Math.max(this.getP1().getY(),this.getP2().getY());
					double sMinX = Math.min(s.getP1().getX(),s.getP2().getX());
					double sMaxX = Math.max(s.getP1().getX(),s.getP2().getX());
					double sMinY = Math.min(s.getP1().getY(),s.getP2().getY());
					double sMaxY = Math.max(s.getP1().getY(),s.getP2().getY());
					//if the intersection found is within the bounds of the segments
					double y = (a2*b1-a1*b2)/(a2-a1);
					double x;
					if(a1!=0){
						x = (y-b1)/a1;
					}else {
						x = (y-b2)/a2;
					}
					
					/*if (x>=thisMinX && x<=thisMaxX &&
							x>=sMinX && x<=sMaxX &&
							y>=thisMinY && y<=thisMaxY &&
							y>=sMinY && y<=sMaxY){*/
						//System.out.println("New point:"+x+" , "+y);
							result = new Point(x,y);
					//}
				} catch (InvalidSlopeException e) {
					e.printStackTrace();
				}	
			}
		}
		return result;
	}
	
	/**
	 * Returns the Segment that is the intersection of this Segment and the one provided
	 * If the method detects the segments are not aligned, it throws an exception
	 * If no intersection at all is found, returns null
	 * 
	 * @param s
	 * @return
	 * @throws IntersectsAsPointException
	 */
	public Segment segmentIntersection(Segment s) throws IntersectsAsPointException{
		Segment result = null;
		double a1 = this.getA();
		double a2 = s.getA();
		double b1;
		double b2;
		double c1;
		double c2;
		
		/***********************************************************************************/
		//if THIS is a point-like segment
		if (a1 == Double.NEGATIVE_INFINITY){
			//if s is also a point-like segment
			if (a2 == Double.NEGATIVE_INFINITY){
				double x = this.getP1().getX();
				double y = this.getP1().getY();
				if(x == s.getP1().getX() && y == s.getP1().getY()){
					result = new Segment(new Point(x,y), new Point(x,y));
				}
			}
			//if s is vertical
			else if (a2 == Double.POSITIVE_INFINITY){
				try{
					c2 = s.getC();
					double x = this.getP1().getX();
					double y = this.getP1().getY();
					double sMinY = Math.min(s.getP1().getY(),s.getP2().getY());
					double sMaxY = Math.max(s.getP1().getY(),s.getP2().getY());
					if(x==c2 && y>=sMinY && y<=sMaxY){
						result = new Segment(new Point(x,y), new Point(x,y));
					}
				} catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
			}
			//if s is not vertical, not point-like
			else{
				try{
					b2 = s.getB();
					double x = this.getP1().getX();
					double y = this.getP1().getY();
					double sMinX = Math.min(s.getP1().getX(),s.getP2().getX());
					double sMaxX = Math.max(s.getP1().getX(),s.getP2().getX());
					double sMinY = Math.min(s.getP1().getY(),s.getP2().getY());
					double sMaxY = Math.max(s.getP1().getY(),s.getP2().getY());
					if(a2*x+b2==y &&
							x>=sMinX && x<=sMaxX &&
							y>=sMinY && y<=sMaxY){
						result = new Segment(new Point(x,y), new Point(x,y));
					}
				}catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
			}
		}
		
		/***********************************************************************************/
		//if a2 is a point-like segment
		else if(a2 == Double.NEGATIVE_INFINITY){
			//if THIS is also a point-like segment
			if (a1 == Double.NEGATIVE_INFINITY){
				double x = s.getP1().getX();
				double y = s.getP1().getY();
				if(x == this.getP1().getX() && y == this.getP1().getY()){
					result = new Segment(new Point(x,y), new Point(x,y));
				}
			}
			//if THIS is vertical
			else if (a1 == Double.POSITIVE_INFINITY){
				try{
					c1 = this.getC();
					double x = s.getP1().getX();
					double y = s.getP1().getY();
					double thisMinY = Math.min(this.getP1().getY(),this.getP2().getY());
					double thisMaxY = Math.max(this.getP1().getY(),this.getP2().getY());
					if(x==c1 && y>=thisMinY && y<=thisMaxY){
						result = new Segment(new Point(x,y), new Point(x,y));
					}
				} catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
			}
			//if THIS is not vertical, not point-like
			else{
				try{
					b1 = this.getB();
					double x = s.getP1().getX();
					double y = s.getP1().getY();
					double thisMinX = Math.min(this.getP1().getX(),this.getP2().getX());
					double thisMaxX = Math.max(this.getP1().getX(),this.getP2().getX());
					double thisMinY = Math.min(this.getP1().getY(),this.getP2().getY());
					double thisMaxY = Math.max(this.getP1().getY(),this.getP2().getY());
					if(a1*x+b1==y &&
							x>=thisMinX && x<=thisMaxX &&
							y>=thisMinY && y<=thisMaxY){
							result = new Segment(new Point(x,y), new Point(x,y));
					}
				}catch (InvalidSlopeException e) {
					e.printStackTrace();
				}
			}
		}
		
		/***********************************************************************************/
		//From here on we assume no point-like segments are involved.
		//if the a's are different, we throw the exception
		else if (a1!=a2){
			throw (new IntersectsAsPointException("Segments not parallel"));
		}
		
		/***********************************************************************************/
		//if both are vertical 
		//TODO: With any other app, this could pose a problem, 
		//      as the order of the points returned has no bearing 
		//      on which one is the "first" on the graph.
		else if (a1 == Double.POSITIVE_INFINITY){
			try {
				c1 = this.getC();
				c2 = s.getC();
				//we check they're properly aligned
				if (c1==c2){
					//and create the segment to be returned as needed
					double y11 = this.getP1().getY();
					double y12 = this.getP2().getY();
					double y21 = s.getP1().getY();
					double y22 = s.getP2().getY();
					
					double top = Math.min(Math.max(y11, y12), Math.max(y21, y22));
					double bottom = Math.max(Math.min(y11, y12), Math.min(y21, y22));
					
					if (bottom <= top){
						result = new Segment(new Point(c1,bottom), new Point(c2,top));
					}
				}
			} catch (InvalidSlopeException e) {
				e.printStackTrace();
			}
		}
		
		/***********************************************************************************/
		//if neither are vertical
		else {	
			try {
				b1 = this.getB();
				b2 = s.getB();
				//we check they're properly aligned
				if (b1 == b2){
					//and create the segment to be returned as needed
					double x11 = this.getP1().getX();
					double x12 = this.getP2().getX();
					double x21 = s.getP1().getX();
					double x22 = s.getP2().getX();
					
					double right = Math.min(Math.max(x11, x12), Math.max(x21, x22));
					double left = Math.max(Math.min(x11, x12), Math.min(x21, x22));
					
					if (left <= right){
						result = new Segment(
								new Point(left,a1*left+b1),
								new Point(right,a1*right+b1)
								);
					}
				}
			} catch (InvalidSlopeException e) {
				e.printStackTrace();
			}
		}	
		return result;
	}
	
	public boolean certainlyPositiveOrNull(){
		return this.getP1().getX()>=0;
	}
	
	public boolean strictlyPositive(){
		return this.getP1().getX()>0;
	}
	
	public boolean certainlyNegativeOrNull(){
		return this.getP2().getX()<=0;
	}
	
	public boolean strictlyNegative(){
		return this.getP2().getX()<=0;
	}
	
	public boolean possiblyNull(){
		return this.getP1().getX()<=0 && this.getP2().getX()>=0;
	}
	
	/**
	 * Must be an horizontal segment
	 * @return opposite 
	 */
	public Segment opposite(){
		Segment opposite = this.clone();
		opposite.getP1().setX(-this.getP2().getX());
		opposite.getP2().setX(-this.getP1().getX());
		return opposite;
	}
	
	public Segment inverse() throws DivisionByZeroException{
		if(this.possiblyNull())
			throw new DivisionByZeroException("Inverse impossible");
		return new Segment(	new Point(1/this.getP2().getX(), this.getP1().getY()),
							new Point(1/this.getP1().getX(),this.getP1().getY()));
	}
	
	/**
	 * Must be horizontal segments
	 * @param other
	 * @return
	 */
	public Segment addition(Segment other){
		return new Segment(	new Point(this.getP1().getX()+other.getP1().getX(),this.getP1().getY()),
							new Point(this.getP2().getX()+other.getP2().getX(),this.getP1().getY()));
	}
	
	public Segment substract(Segment other){
		return this.addition(other.opposite());
	}
	
	public Segment multiply(Segment other){
		double xInf = this.getP1().getX();
		double xSup = this.getP2().getX();
		double y = this.getP1().getY();
		double xInfOther = other.getP1().getX();
		double xSupOther = other.getP2().getX();
		double xInfResult = Math.min(xInf*xSupOther, xSup*xInfOther);
		double xSupResult = Math.max(xInf*xInfOther, xSup*xSupOther);
		return new Segment( new Point(xInfResult, y), new Point(xSupResult, y));		
	}
	
	public Segment divide(Segment other) throws DivisionByZeroException{
		return this.multiply(other.inverse());
	}
	
	//y = ax + b
	//OR
	//x = c
	/**
	 * 
	 * @return the slope of the segment.
	 * the slope is POSITIVE_INFINITY if the segment is vertical, and
	 * NEGATIVE_INFINITY if it is a point
	 */
	public double getA(){
		double dx = this.p2.getX()-this.p1.getX();
		double dy = this.p2.getY()-this.p1.getY();
		if (dx == 0){
			if (dy == 0){
				return Double.NEGATIVE_INFINITY;
			}else {
				return Double.POSITIVE_INFINITY;
			}
		}
		else {
			return dy/dx;
		}
	}
	
	public double getB() throws InvalidSlopeException{
		double a = this.getA();
		if (a == Double.POSITIVE_INFINITY ||a == Double.NEGATIVE_INFINITY){
			throw (new InvalidSlopeException("a is infinite"));
		}
		else{
			return p1.getY()-a*p1.getX(); 
		}
		
	}
	
	public double getC() throws InvalidSlopeException{
		double a = this.getA();
		if (a != Double.POSITIVE_INFINITY){
			throw (new InvalidSlopeException("a is not positive infinite"));
		}
		else{
			return p1.getX(); 
		}
	}

	public Point getP1() {
		return p1;
	}

	public void setP1(Point p1) {
		this.p1 = p1;
	}

	public Point getP2() {
		return p2;
	}

	public void setP2(Point p2) {
		this.p2 = p2;
	}
	
	public Segment clone(){
		return new Segment(	new Point(this.getP1().getX(),
								this.getP1().getY()),
							new Point(this.getP2().getX(),
								this.getP2().getY())
							);
	}
	
	public String toString(){
		return "("+p1.getX()+","+p1.getY()+")"+"("+p2.getX()+","+p2.getY()+")";
	}
}
